package com.yyfax.pay.third.facade.fuioudeposit;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yyfax.framework.common.Log;
import com.yyfax.pay.common.enums.PayModeEnum;
import com.yyfax.pay.common.enums.PayTypeEnum;
import com.yyfax.pay.common.enums.PaymentEnum;
import com.yyfax.pay.common.exception.PayExceptionCode;
import com.yyfax.pay.common.util.AliasUtil;
import com.yyfax.pay.config.data.service.MerchantPaymentService;
import com.yyfax.pay.third.model.to.fuioudeposit.FuiouDepositConfigExt;

/**
 * 富友存管验签服务
 * 
 * @author buyi
 * @date 2017-08-31
 * @since v1.0.0
 */
@Service
public class FuiouSignService {
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	/** 私钥本地缓存，key-商户号，value-证书实例 */
	private static final Map<String, PrivateKey> privateKeyMap = Maps.newHashMap();

	/** 公钥本地缓存，key-商户号，value-证书实例 */
	private static final Map<String, PublicKey> publicKeyMap = Maps.newHashMap();

	@Resource
	private MerchantPaymentService merchantPaymentService;

	@PostConstruct
	public void addProvider() {
		try {
			java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
		} catch (Exception e) {
			logger.error(Log.op("FuiouSignService.addProvider").msg("密钥初始化失败, 缺少jce依赖").toString(), e);
			throw e;
		}
	}

	/**
	 * @author buyi
	 * @date 2017-09-02 14:19:55
	 * @since v2.0.0
	 * @param merchantCode
	 * @return
	 * @throws Exception
	 */
	public FuiouDepositConfigExt getFuiouDepositConfig(String merchantCode) {
		FuiouDepositConfigExt depositConfigExt;
		try {
			depositConfigExt = merchantPaymentService.queryByThirdMerchantCodeForExt(merchantCode, PaymentEnum.FUIOU_DEPOSIT.getValue(),
					PayTypeEnum.COMMON.getValue(), PayModeEnum.AUTH_PAY.getValue(), FuiouDepositConfigExt.class);
		} catch (Exception e) {
			logger.error(Log.op("FuiouSignService.getFuiouDepositConfig").msg("查询商户富友存管支付凭证信息异常").kv("merchantCode", merchantCode).toString(), e);
			throw PayExceptionCode.SYS_ERROR.exp();
		}

		if (depositConfigExt == null) {
			throw PayExceptionCode.ERROR_ILL_PARAMETER.expMsg("merchantPayment缺少配置,merchantCode:" + merchantCode);
		}

		return depositConfigExt;
	}

	/**
	 * 签名字符串
	 * 
	 * @author buyi
	 * @date 2017-09-02 14:32:44
	 * @since v2.0.0
	 * @param merchantCode
	 * @param inputStr
	 * @return
	 */
	public String sign(String merchantCode, String inputStr) {
		String result = null;
		try {
			byte[] tByte;
			Signature signature = Signature.getInstance("SHA1withRSA", "BC");
			signature.initSign(getPrivateKey(merchantCode));
			signature.update(inputStr.getBytes("UTF-8"));
			tByte = signature.sign();
			result = Base64.encodeBase64String(tByte);
			logger.debug(Log.op("FuiouSignService.sign").msg("富有存管签名-签名明文").kv("merchantCode", merchantCode).kv("明文", inputStr).toString());
			logger.debug(Log.op("FuiouSignService.sign").msg("富有存管签名-签名密文").kv("merchantCode", merchantCode).kv("密文", result).toString());
		} catch (Exception e) {
			logger.error(Log.op("FuiouSignService.sign").msg("签名字符串异常").kv("merchantCode", merchantCode).kv("密文", result).toString());
			throw PayExceptionCode.SYS_ERROR.expMsg("签名异常");
		}

		return result;
	}

	/**
	 * 获取私钥
	 * 
	 * @author buyi
	 * @date 2017-09-02 14:32:59
	 * @since v2.0.0
	 * @param merchantCode
	 * @return
	 * @throws Exception
	 */
	private PrivateKey getPrivateKey(String merchantCode) throws Exception {
		if (privateKeyMap.containsKey(merchantCode)) {
			// 如果商户号路径存在
			return privateKeyMap.get(merchantCode);
		}

		return initPrivateKey(merchantCode);
	}

	/**
	 * 同步加载私钥
	 * 
	 * @author buyi
	 * @date 2017-09-02 14:33:13
	 * @since v2.0.0
	 * @param merchantCode
	 * @return
	 * @throws Exception
	 */
	private synchronized PrivateKey initPrivateKey(String merchantCode) throws Exception {
		if (privateKeyMap.containsKey(merchantCode)) {
			// 如果商户号路径存在
			return privateKeyMap.get(merchantCode);
		}

		FuiouDepositConfigExt configExt = getFuiouDepositConfig(merchantCode);

		// 初始化证书
		PrivateKey privateKey = loadPrivateKey(configExt.getPrivateKeyPath());
		if (privateKey == null) {
			logger.error(Log.op("FuiouSignService.initPrivateKey").msg("初始化PrivateKey为null").kv("merchantCode", merchantCode).toString());
			throw PayExceptionCode.SYS_ERROR.expMsg("初始化证书异常");
		}

		privateKeyMap.put(merchantCode, privateKey);

		return privateKey;
	}

	/**
	 * 加载私钥
	 * 
	 * @author buyi
	 * @date 2017-09-02 14:33:24
	 * @since v2.0.0
	 * @param filePath
	 * @return
	 */
	private PrivateKey loadPrivateKey(String filePath) {
		KeyFactory kf;
		PrivateKey privateKey = null;
		try {
			String base64edKey = readFile(filePath);

			kf = KeyFactory.getInstance("RSA", "BC");
			PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(base64edKey));
			privateKey = kf.generatePrivate(keySpec);
		} catch (Exception e) {
			logger.error(Log.op("FuiouSignService.loadPrivateKey").msg("初始化证书PrivateKey异常").kv("filePath", filePath).toString(), e);
			throw PayExceptionCode.SYS_ERROR.expMsg("初始化证书异常");
		}
		return privateKey;
	}

	/**
	 * 对富友返回的数据进行验签
	 *
	 * @param src 返回数据明文
	 * @param signValue 返回数据签名
	 * @return
	 */
	public boolean verifySign(String merchantCode, String src, String signValue) {
		boolean bool = false;
		try {
			Signature signature = Signature.getInstance("SHA1withRSA", "BC");
			signature.initVerify(getPublicKey(merchantCode));
			signature.update(src.getBytes("UTF-8"));
			logger.debug("富有存管验签-签名明文：{}", src);
			logger.debug("富有存管验签-签名密文：{}", signValue);
			bool = signature.verify(Base64.decodeBase64(signValue));
		} catch (Exception e) {
			logger.error(Log.op("FuiouSignService.loadPrivateKey").msg("验签异常").kv("merchantCode", merchantCode).toString(), e);
		}
		return bool;
	}

	/**
	 * 获取公钥
	 * 
	 * @author buyi
	 * @date 2017-09-02 14:32:59
	 * @since v2.0.0
	 * @param merchantCode
	 * @return
	 * @throws Exception
	 */
	private PublicKey getPublicKey(String merchantCode) throws Exception {
		if (publicKeyMap.containsKey(merchantCode)) {
			// 如果存在相应商户证书
			return publicKeyMap.get(merchantCode);
		}

		return initPublicKey(merchantCode);
	}

	/**
	 * 同步加载公钥
	 * 
	 * @author buyi
	 * @date 2017-09-02 14:33:13
	 * @since v2.0.0
	 * @param merchantCode
	 * @return
	 * @throws Exception
	 */
	private synchronized PublicKey initPublicKey(String merchantCode) throws Exception {
		if (publicKeyMap.containsKey(merchantCode)) {
			// 如果存在相应商户证书
			return publicKeyMap.get(merchantCode);
		}

		FuiouDepositConfigExt configExt = getFuiouDepositConfig(merchantCode);

		PublicKey publicKey = loadPublicKey(configExt.getPublicKeyPath());

		if (publicKey == null) {
			logger.error(Log.op("FuiouSignService.initPublicKey").msg("初始化publicKey为null").kv("merchantCode", merchantCode).toString());
			throw PayExceptionCode.SYS_ERROR.expMsg("初始化证书异常");
		}

		publicKeyMap.put(merchantCode, publicKey);

		return publicKey;
	}

	/**
	 * 加载私钥
	 * 
	 * @author buyi
	 * @date 2017-09-02 14:33:24
	 * @since v2.0.0
	 * @param filePath
	 * @return
	 */
	private PublicKey loadPublicKey(String filePath) {
		KeyFactory kf;
		PublicKey publickey = null;
		try {
			String base64edKey = readFile(filePath);

			kf = KeyFactory.getInstance("RSA", "BC");
			X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(base64edKey));
			publickey = kf.generatePublic(keySpec);
		} catch (Exception e) {
			logger.error(Log.op("FuiouSignService.loadPublicKey").msg("初始化证书PublicKey异常").kv("filePath", filePath).toString(), e);
			throw PayExceptionCode.SYS_ERROR.expMsg("初始化证书异常");
		}
		return publickey;
	}

	/**
	 * 读取文件
	 * 
	 * @author buyi
	 * @date 2017-09-29 17:33:47
	 * @since v2.0.0
	 * @param fileName
	 * @return
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	private String readFile(String fileName) throws FileNotFoundException, IOException {
		File f = new File(fileName);
		if (!f.exists()) {
			logger.error(Log.op("FuiouSignService.readFile").msg("证书路径不存在").kv("fileName", fileName).toString());
			throw new FileNotFoundException("文件不存在" + fileName);
		}

		try (FileInputStream in = new FileInputStream(f)) {
			return IOUtils.toString(in, CharEncoding.UTF_8);
		}
	}

	/**
	 * 新增获取实体对象签名串
	 * 
	 * @author buyi
	 * @date 2017-09-01 10:30:05
	 * @since v2.0.0
	 * @param obj
	 * @return
	 */
	public String getSign(String merchantCode, Object obj) {
		String str = AliasUtil.mergeObjectValues(obj, "|");
		return sign(merchantCode, str);
	}

	/**
	 * 获取签名值<br>
	 * 
	 * @author buyi
	 * @date 2018-06-14 14:33:11
	 * @since v2.2.2
	 * @param merchantCode
	 * @param reqMap
	 * @return
	 */
	public String getSignMap(String merchantCode, Map<String, String> map) {
		List<String> names = Lists.newArrayList(map.keySet());
		List<String> values = Lists.newArrayList();
		//按照字段排序
		Collections.sort(names, String.CASE_INSENSITIVE_ORDER);
		for (String name : names) {
			values.add(map.get(name));
		}
		
		String str = StringUtils.join(values, "|");

		return sign(merchantCode, str);
	}
}
